// SPDX-License-Identifier: GPL-2.0

/**
 * Copyright (C) 2025 Huawei Technologies Co., Ltd.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, see <https://www.gnu.org/licenses/>.
 */

#include "cmanuapi.h"

#include <errno.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

/* Driver-based implementation */

#define CMAN_MAGIC  'x'

#pragma pack(push, 8)
typedef struct l2_partition_ioctl_args
{
    int cpu;
    unsigned long l2_partition_dmask;
    unsigned long l2_partition_imask;
} l2_partition_ioctl_args_t;
#pragma pack(pop)

#define CMAN_L2_PART_GET            _IOWR(CMAN_MAGIC, 3, l2_partition_ioctl_args_t)
#define CMAN_L2_PART_SET            _IOW(CMAN_MAGIC, 4, l2_partition_ioctl_args_t)
#define CMAN_VERSION                _IOR(CMAN_MAGIC, 6, int)
#define CMAN_HW_INFO                _IOR(CMAN_MAGIC, 12, hw_info_ioctl_args_t)
#define CMAN_RESET_L2_PART          _IOW(CMAN_MAGIC, 14, int)

int fd = -1;
static hw_info_ioctl_args_t cman_uapi_hw_info_data;

static int open_cman_device(void)
{
    return open("/dev/cman_dev", O_RDWR);
}

static l2_partition_ioctl_args_t populate_l2_partition_ioctl_args(int cpu, unsigned long dmask, unsigned long imask)
{
    l2_partition_ioctl_args_t args;

    args.cpu = cpu;
    args.l2_partition_dmask = dmask;
    args.l2_partition_imask = imask;

    return args;
}

static void fini_DRV(void)
{
    close(fd);
    fd = -1;
}

static int cman_uapi_init_DRV(void)
{
    int e = 0, version = 0;
    errno = 0;
    hw_info_ioctl_args_t args;

    fd = open_cman_device();
    if (fd < 0)
    {
        switch (errno)
        {
            case EBUSY:
                break;
            case EMFILE:
                break;
            default:
                errno = ENODEV;
        }
        return -1;
    }


    e = ioctl(fd, CMAN_VERSION, &version);

    if (e) {
        fini_DRV();
        errno = e;
        return -1;
    }

    if (version != CMANUAPI_VER) {
        fini_DRV();
        errno = EBADF;
        return -1;
    }

    e = ioctl(fd, CMAN_HW_INFO, &args);
    if (e) {
        fini_DRV();
        errno = e;
        return -1;
    }
    cman_uapi_hw_info_data = args;

    errno = e;

    return 0;
}

static ssize_t cman_uapi_l2_partition_get_DRV(int cpu, unsigned long* dmask, unsigned long* imask)
{
    int e = 0, result = 0;
    errno = 0;

    if (dmask == NULL || imask == NULL)
    {
        errno = EINVAL;
        return -1;
    }

    l2_partition_ioctl_args_t args = populate_l2_partition_ioctl_args(cpu, 0, 0);

    e = ioctl(fd, CMAN_L2_PART_GET, &args);
    if (e) {
        result = -1;
    } else {
        *dmask = args.l2_partition_dmask;
        *imask = args.l2_partition_imask;
    }
    errno = e;

    return result;
}

static ssize_t cman_uapi_l2_partition_set_DRV(int cpu, unsigned long dmask, unsigned long imask)
{
    int e = 0, result = 0;
    errno = 0;

    l2_partition_ioctl_args_t args = populate_l2_partition_ioctl_args(cpu, dmask, imask);

    e = ioctl(fd, CMAN_L2_PART_SET, &args);
    if (e)
        result = -1;

    errno = e;

    return result;
}

static hw_info_ioctl_args_t cman_uapi_hw_info_DRV(void)
{
    return cman_uapi_hw_info_data;
}

static int cman_uapi_reset_l2_partition_DRV(int cpu)
{
    int e = 0, result = 0;
    errno = 0;

    e = ioctl(fd, CMAN_RESET_L2_PART, &cpu);
    if (e)
        result = -1;

    errno = e;

    return result;
}

typedef int     (*cman_uapi_init_t)(void);
typedef ssize_t (*cman_uapi_l2_partition_get_t)(int cpu, unsigned long* dmask, unsigned long* imask);
typedef ssize_t (*cman_uapi_l2_partition_set_t)(int cpu, unsigned long dmask, unsigned long imask);
typedef hw_info_ioctl_args_t (*cman_uapi_hw_info_t)(void);
typedef int     (*cman_uapi_reset_l2_partition_t)(int cpu);
typedef void    (*fini_t)(void);

static struct
{
    cman_uapi_init_t                    cman_uapi_init;
    cman_uapi_l2_partition_get_t        cman_uapi_l2_partition_get;
    cman_uapi_l2_partition_set_t        cman_uapi_l2_partition_set;
    cman_uapi_hw_info_t                 cman_uapi_hw_info;
    cman_uapi_reset_l2_partition_t      cman_uapi_reset_l2_partition;
    fini_t                              fini;
} dispatch;

int cman_uapi_init(void)
{
    static int initialized = 0;
    int ret = 0;
    static pthread_mutex_t initialization_lock = PTHREAD_MUTEX_INITIALIZER;

    if (!initialized)
    {
        pthread_mutex_lock(&initialization_lock);
        if (!initialized)
        {
            {
                dispatch.cman_uapi_init = cman_uapi_init_DRV;
                dispatch.cman_uapi_l2_partition_get = cman_uapi_l2_partition_get_DRV;
                dispatch.cman_uapi_l2_partition_set = cman_uapi_l2_partition_set_DRV;
                dispatch.cman_uapi_hw_info = cman_uapi_hw_info_DRV;
                dispatch.cman_uapi_reset_l2_partition = cman_uapi_reset_l2_partition_DRV;
                dispatch.fini = fini_DRV;
            }

            ret = dispatch.cman_uapi_init();
            initialized = 1;
        }
        pthread_mutex_unlock(&initialization_lock);
    }

    return ret;
}

int check_initialized(void)
{
    if (fd < 0) {
        errno = EIDRM;
        return -1;
    }

    return 0;
}

static void fini(void) __attribute__((destructor));

static void fini(void)
{
    if (check_initialized() < 0)
        return;

    return dispatch.fini();
}

ssize_t cman_uapi_l2_partition_get(int cpu, unsigned long* dmask, unsigned long* imask)
{
    if (check_initialized() < 0)
        return -1;

    return dispatch.cman_uapi_l2_partition_get(cpu, dmask, imask);
}

ssize_t cman_uapi_l2_partition_set(int cpu, unsigned long dmask, unsigned long imask)
{
    if (check_initialized() < 0)
        return -1;

    return dispatch.cman_uapi_l2_partition_set(cpu, dmask, imask);
}

hw_info_ioctl_args_t cman_uapi_hw_info(void)
{
    hw_info_ioctl_args_t error_ret = {
        .l2_segment_size = -1,
        .l2_capacity = 0 };

    if (check_initialized() < 0)
        return error_ret;

    return dispatch.cman_uapi_hw_info();
}

int cman_uapi_reset_l2_partition(int cpu)
{
    if (check_initialized() < 0)
        return -1;

    return dispatch.cman_uapi_reset_l2_partition(cpu);
}
